Swift 2.0 Replicate Objc_Association_Retain

Swift 2.0 replicate OBJC_ASSOCIATION_RETAIN

This is actually now imported into Swift as an enum named objc_AssociationPolicy. Definition:

enum objc_AssociationPolicy : UInt {
case OBJC_ASSOCIATION_ASSIGN
case OBJC_ASSOCIATION_RETAIN_NONATOMIC
case OBJC_ASSOCIATION_COPY_NONATOMIC
case OBJC_ASSOCIATION_RETAIN
case OBJC_ASSOCIATION_COPY
}

Meaning that it can be used as follows.

objc_setAssociatedObject(host, key, associatedProperty, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)

Or with enum shorthand syntax.

objc_setAssociatedObject(host, key, associatedProperty, .OBJC_ASSOCIATION_RETAIN)

Note that objc_setAssociatedObject has also been updated to take a objc_AssociationPolicy argument instead of UInt making it unnecessary to access the enum's rawValue here.

Swift 2 : OBJC_ASSOCIATION_RETAIN_NONATOMIC use of unresolved identifier

If you look at the obj c runtime swift header, it appears this construct has become an enum:

/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
enum objc_AssociationPolicy : UInt {

case OBJC_ASSOCIATION_ASSIGN
case OBJC_ASSOCIATION_RETAIN_NONATOMIC

case OBJC_ASSOCIATION_COPY_NONATOMIC

case OBJC_ASSOCIATION_RETAIN

case OBJC_ASSOCIATION_COPY
}

So you can replace with: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC.

If you need the token as a UInt, you can always use .rawValue.

(In the previous version objc_AssociationPolicy was just a typealias for UInt - with the effect of casting 'OBJC_ASSOCIATION_RETAIN_NONATOMIC', an Int)

associated property in swift 2.0

there is more trouble in your code, but most important is, that AnyObject is a protocol to which all classes implicitly conform. your ShareCompletion is not a class at all

import Foundation
public typealias ShareCompletion = (result: AnyObject, error : NSError?) -> Void
private var ShareCompletionKey = "ShareCompletionKey"

let sc: ShareCompletion = { result, error in }
print(sc.dynamicType) // (AnyObject, Optional<NSError>) -> ()
sc is AnyObject // FALSE !!!!!

How to have stored properties in Swift, the same way I had on Objective-C?

Associated objects API is a bit cumbersome to use. You can remove most of the boilerplate with a helper class.

public final class ObjectAssociation<T: AnyObject> {

private let policy: objc_AssociationPolicy

/// - Parameter policy: An association policy that will be used when linking objects.
public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

self.policy = policy
}

/// Accesses associated object.
/// - Parameter index: An object whose associated object is to be accessed.
public subscript(index: AnyObject) -> T? {

get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
}
}

Provided that you can "add" a property to objective-c class in a more readable manner:

extension SomeType {

private static let association = ObjectAssociation<NSObject>()

var simulatedProperty: NSObject? {

get { return SomeType.association[self] }
set { SomeType.association[self] = newValue }
}
}

As for the solution:

extension CALayer {

private static let initialPathAssociation = ObjectAssociation<CGPath>()
private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()

var initialPath: CGPath! {
get { return CALayer.initialPathAssociation[self] }
set { CALayer.initialPathAssociation[self] = newValue }
}

var shapeLayer: CAShapeLayer? {
get { return CALayer.shapeLayerAssociation[self] }
set { CALayer.shapeLayerAssociation[self] = newValue }
}
}

setting new properties in category interface/implementation

It is not possible to add members and properties to an existing class via a category — only methods.

https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html

One possible workaround is to write "setter/getter-like" methods, that uses a singleton to save the variables, that would had been the member.

-(void)setMember:(MyObject *)someObject
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
[dict setObject:someObject forKey:self];
}

-(MyObject *)member
{
NSMutableDictionary *dict = [MySingleton sharedRegistry];
return [dict objectforKey:self];
}

or — of course — write a custom class, that inherits from UILabel


Note that nowadays an associated object can be injected during runtime. The Objective C Programming Language: Associative References

Is there a way to request multiple distinct resources in parallel using URLSession.shared.dataTask

You ask:

Is there a way to request multiple distinct resources in parallel using URLSession.shared.dataTask

By default, it does perform requests in parallel.

Let’s step back for a second: In your prior question, you were asking how to implement a Kingfisher-like UIImageView extension. In my answer, I mentioned using objc_getAssociatedObject and objc_setAssociatedObject to achieve that. But in your question here, you’ve taken that associated object logic and put it in your DataRequest object.

Your thought process, to pull the asynchronous image retrieval logic out of the UIImageView is a good idea: You may want to request images for buttons. You might a general “fetch image asynchronously” routine, completely separate from any UIKit objects. So abstracting the network layer code out of the extension is an excellent idea.

But the whole idea behind asynchronous image retrieval UIImageView/UIButton extensions is that we want a UIKit control where not only can it perform asynchronous requests, but that if the cell with the control is reused, that it will cancel the prior asynchronous request (if any) before starting the next one. That way, if we scroll quickly down to images 80 through 99, the requests for cells 0 through 79 will be canceled, and the visible images won’t get backlogged behind all these old image requests.

But to achieve that, that means that the control needs some way to keep track of the prior request for that reused cell somehow. And because we can’t add stored properties in a UIImageView extension, that’s why we use the objc_getAssociatedObject and objc_setAssociatedObject pattern. But that has to be in the image view.

Unfortunately, in your code above, the associated object is in your DataRequest object. First, as I’ve tried to outline, the whole idea is that the image view must keep track of the prior request for that control. Putting this “keep track of the prior request” inside the DataRequest object defeats that purpose. Second, it’s worth noting that you don’t need associated objects in your own types, like DataRequest. You’d just have a stored property. You only need to go through this associated object silliness when extending another type, such as UIImageView.

Below, is a quick example that I whipped together showing a UIImageView extension for asynchronous image retrieval. Note, this doesn’t have the abstraction of the network code out of the extension, but do note that the associated object logic to keep track of the prior request must remain with the extension.

private var taskKey: Void?

extension UIImageView {
private static let imageProcessingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".imageprocessing", attributes: .concurrent)

private var savedTask: URLSessionTask? {
get { return objc_getAssociatedObject(self, &taskKey) as? URLSessionTask }
set { objc_setAssociatedObject(self, &taskKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
}

/// Set image asynchronously.
///
/// - Parameters:
/// - url: `URL` for image resource.
/// - placeholder: `UIImage` of placeholder image. If not supplied, `image` will be set to `nil` while request is underway.
/// - shouldResize: Whether the image should be scaled to the size of the image view. Defaults to `true`.

func setImage(_ url: URL, placeholder: UIImage? = nil, shouldResize: Bool = true) {
savedTask?.cancel()
savedTask = nil

image = placeholder
if let image = ImageCache.shared[url] {
DispatchQueue.main.async {
UIView.transition(with: self, duration: 0.1, options: .transitionCrossDissolve, animations: {
self.image = image
}, completion: nil)
}
return
}

var task: URLSessionTask!
let size = bounds.size * UIScreen.main.scale
task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
guard
error == nil,
let httpResponse = response as? HTTPURLResponse,
(200..<300) ~= httpResponse.statusCode,
let data = data
else {
return
}

UIImageView.imageProcessingQueue.async { [weak self] in
var image = UIImage(data: data)
if shouldResize {
image = image?.scaledAspectFit(to: size)
}

ImageCache.shared[url] = image

DispatchQueue.main.async {
guard
let self = self,
let savedTask = self.savedTask,
savedTask.taskIdentifier == task.taskIdentifier
else {
return
}
self.savedTask = nil

UIView.transition(with: self, duration: 0.1, options: .transitionCrossDissolve, animations: {
self.image = image
}, completion: nil)
}
}
}
task.resume()
savedTask = task
}
}

class ImageCache {
static let shared = ImageCache()

private let cache = NSCache<NSURL, UIImage>()
private var observer: NSObjectProtocol?

init() {
observer = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] _ in
self?.cache.removeAllObjects()
}
}

deinit {
NotificationCenter.default.removeObserver(observer!)
}

subscript(url: URL) -> UIImage? {
get {
return cache.object(forKey: url as NSURL)
}

set {
if let data = newValue {
cache.setObject(data, forKey: url as NSURL)
} else {
cache.removeObject(forKey: url as NSURL)
}
}
}
}

And this is my resizing routine:

extension UIImage {

/// Resize the image to be the required size, stretching it as needed.
///
/// - parameter newSize: The new size of the image.
/// - parameter contentMode: The `UIView.ContentMode` to be applied when resizing image.
/// Either `.scaleToFill`, `.scaleAspectFill`, or `.scaleAspectFit`.
///
/// - returns: Return `UIImage` of resized image.

func scaled(to newSize: CGSize, contentMode: UIView.ContentMode = .scaleToFill) -> UIImage? {
switch contentMode {
case .scaleToFill:
return filled(to: newSize)

case .scaleAspectFill, .scaleAspectFit:
let horizontalRatio = size.width / newSize.width
let verticalRatio = size.height / newSize.height

let ratio: CGFloat!
if contentMode == .scaleAspectFill {
ratio = min(horizontalRatio, verticalRatio)
} else {
ratio = max(horizontalRatio, verticalRatio)
}

let sizeForAspectScale = CGSize(width: size.width / ratio, height: size.height / ratio)
let image = filled(to: sizeForAspectScale)
let doesAspectFitNeedCropping = contentMode == .scaleAspectFit && (newSize.width > sizeForAspectScale.width || newSize.height > sizeForAspectScale.height)
if contentMode == .scaleAspectFill || doesAspectFitNeedCropping {
let subRect = CGRect(
x: floor((sizeForAspectScale.width - newSize.width) / 2.0),
y: floor((sizeForAspectScale.height - newSize.height) / 2.0),
width: newSize.width,
height: newSize.height)
return image?.cropped(to: subRect)
}
return image

default:
return nil
}
}

/// Resize the image to be the required size, stretching it as needed.
///
/// - parameter newSize: The new size of the image.
///
/// - returns: Resized `UIImage` of resized image.

func filled(to newSize: CGSize) -> UIImage? {
let format = UIGraphicsImageRendererFormat()
format.opaque = false
format.scale = scale

return UIGraphicsImageRenderer(size: newSize, format: format).image { _ in
draw(in: CGRect(origin: .zero, size: newSize))
}
}

/// Crop the image to be the required size.
///
/// - parameter bounds: The bounds to which the new image should be cropped.
///
/// - returns: Cropped `UIImage`.

func cropped(to bounds: CGRect) -> UIImage? {
// if bounds is entirely within image, do simple CGImage `cropping` ...

if CGRect(origin: .zero, size: size).contains(bounds) {
return cgImage?.cropping(to: bounds * scale).flatMap {
UIImage(cgImage: $0, scale: scale, orientation: imageOrientation)
}
}

// ... otherwise, manually render whole image, only drawing what we need

let format = UIGraphicsImageRendererFormat()
format.opaque = false
format.scale = scale

return UIGraphicsImageRenderer(size: bounds.size, format: format).image { _ in
let origin = CGPoint(x: -bounds.minX, y: -bounds.minY)
draw(in: CGRect(origin: origin, size: size))
}
}

/// Resize the image to fill the rectange of the specified size, preserving the aspect ratio, trimming if needed.
///
/// - parameter newSize: The new size of the image.
///
/// - returns: Return `UIImage` of resized image.

func scaledAspectFill(to newSize: CGSize) -> UIImage? {
return scaled(to: newSize, contentMode: .scaleAspectFill)
}

/// Resize the image to fit within the required size, preserving the aspect ratio, with no trimming taking place.
///
/// - parameter newSize: The new size of the image.
///
/// - returns: Return `UIImage` of resized image.

func scaledAspectFit(to newSize: CGSize) -> UIImage? {
return scaled(to: newSize, contentMode: .scaleAspectFit)
}

/// Create smaller image from `Data`
///
/// - Parameters:
/// - data: The image `Data`.
/// - maxSize: The maximum edge size.
/// - scale: The scale of the image (defaults to device scale if 0 or omitted.
/// - Returns: The scaled `UIImage`.

class func thumbnail(from data: Data, maxSize: CGFloat, scale: CGFloat = 0) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else {
return nil
}

return thumbnail(from: imageSource, maxSize: maxSize, scale: scale)
}

/// Create smaller image from `URL`
///
/// - Parameters:
/// - data: The image file URL.
/// - maxSize: The maximum edge size.
/// - scale: The scale of the image (defaults to device scale if 0 or omitted.
/// - Returns: The scaled `UIImage`.

class func thumbnail(from fileURL: URL, maxSize: CGFloat, scale: CGFloat = 0) -> UIImage? {
guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) else {
return nil
}

return thumbnail(from: imageSource, maxSize: maxSize, scale: scale)
}

private class func thumbnail(from imageSource: CGImageSource, maxSize: CGFloat, scale: CGFloat) -> UIImage? {
let scale = scale == 0 ? UIScreen.main.scale : scale
let options: [NSString: Any] = [
kCGImageSourceThumbnailMaxPixelSize: maxSize * scale,
kCGImageSourceCreateThumbnailFromImageAlways: true
]
if let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
return UIImage(cgImage: scaledImage, scale: scale, orientation: .up)
}
return nil
}

}

extension CGSize {
static func * (lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
}

extension CGPoint {
static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
}
}

extension CGRect {
static func * (lhs: CGRect, rhs: CGFloat) -> CGRect {
return CGRect(origin: lhs.origin * rhs, size: lhs.size * rhs)
}
}

That having been said, we really should constrain our concurrent requests to something reasonable (4-6 at a time) so that they don’t try to start until the prior requests are done (or are canceled) to avoid timeouts. The typical solution is wrapping the requests with asynchronous Operation subclasses, add them to an operation queue, and constrain the maxConcurrentOperationCount to whatever value you choose.



Related Topics



Leave a reply



Submit