There are many example codes like this:
JSContext *context = [[JSContext alloc] init];
context[#"globalFunc"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(#"got:%#", obj);
}
};
What confusing me is the context[#"globalFunc"] = *** statement . It looks like that JSContext is supporting subscripting (operator [ ])?
But I don't found any official documents say that ? So how do we know we can do subscripting in JSContext to set/get objects from it ?
OK ,I miss the extension code just after JSContext's declaration -_-
#interface JSContext (SubscriptSupport)
/*!
#method
#abstract Get a particular property on the global object.
#result The JSValue for the global object's property.
*/
- (JSValue *)objectForKeyedSubscript:(id)key;
/*!
#method
#abstract Set a particular property on the global object.
*/
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;
#end
Related
In my macOS Objective-C application, I have created a subclass of NSMutableSet. What I want to achieve is a NSMutableSet that does not use isEqual: as the comparing strategy. Specifically, The set will contain objects of type NSRunningApplication, and I want the set to work based on the equality of the objects bundle identifiers. Following is my implementation:
Header file:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface BundleIdentifierAwareMutableSet : NSMutableSet
#property (atomic, strong) NSMutableSet *backStorageMutableSet;
#property (atomic, strong) NSMutableArray *backStorageMutableArray;
#end
NS_ASSUME_NONNULL_END
Implementation file:
#import "BundleIdentifierAwareMutableSet.h"
#implementation BundleIdentifierAwareMutableSet
#synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(#"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
This seems to work, and indeed, When applicable, Xcode logs that an ordinary NSMutableSet would have not considered two members equal. I would like to bring this implementation to the Production App, but I am afraid I have not considered something important, since this is the first time I subclass NSMutableSet. For example, I am worried about the following method:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
This is the only use I do of the backStorageMutableSet since the rest is backed to the array. Is this fine or can bring troubles ? Will other parts of the subclass bring problems ? Any help will be greatly appreciated. Thanks
Don't do this. Subclassing collections should be the last resort. It can have implications on performance, ... Try to use highest possible abstraction and go down if it doesn't work for you for some reason.
Wrapper object
Wrap the NSRunningApplication in another object and provide your own hash & isEqual: methods.
Application.h:
#interface Application: NSObject
#property (nonatomic, strong, readonly, nonnull) NSRunningApplication *application;
#end
Application.m:
#interface Application ()
#property (nonatomic, strong, nonnull) NSRunningApplication *application;
#end
#implementation Application
- (nonnull instancetype)initWithRunningApplication:(NSRunningApplication *_Nonnull)application {
if ((self = [super init]) == nil) {
// https://developer.apple.com/documentation/objectivec/nsobject/1418641-init?language=objc
//
// The init() method defined in the NSObject class does no initialization; it simply
// returns self. In terms of nullability, callers can assume that the NSObject
// implementation of init() does not return nil.
return nil;
}
self.application = application;
return self;
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418795-isequal?language=objc
- (BOOL)isEqual:(id)object {
if (![object isKindOfClass:[Application class]]) {
return NO;
}
Application *app = (Application *)object;
return [self.application.bundleIdentifier isEqualToString:app.application.bundleIdentifier];
}
// https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash?language=objc
- (NSUInteger)hash {
return self.application.bundleIdentifier.hash;
}
#end
Toll-free bridging & CFMutableSetRef
CFSet is bridged with the NSSet, CFMutableSet is bridged with the NSMutableSet, etc. It means that you can create a set via Core Foundation
API and then use it as NSSet for example. Core Foundation is a powerful
framework which exposes more stuff to you.
You can provide a custom set of callbacks for the CFSet.
/*!
#typedef CFSetCallBacks
Structure containing the callbacks of a CFSet.
#field version The version number of the structure type being passed
in as a parameter to the CFSet creation functions. This
structure is version 0.
#field retain The callback used to add a retain for the set on
values as they are put into the set. This callback returns
the value to store in the set, which is usually the value
parameter passed to this callback, but may be a different
value if a different value should be stored in the set.
The set's allocator is passed as the first argument.
#field release The callback used to remove a retain previously added
for the set from values as they are removed from the
set. The set's allocator is passed as the first
argument.
#field copyDescription The callback used to create a descriptive
string representation of each value in the set. This is
used by the CFCopyDescription() function.
#field equal The callback used to compare values in the set for
equality for some operations.
#field hash The callback used to compare values in the set for
uniqueness for some operations.
*/
typedef struct {
CFIndex version;
CFSetRetainCallBack retain;
CFSetReleaseCallBack release;
CFSetCopyDescriptionCallBack copyDescription;
CFSetEqualCallBack equal;
CFSetHashCallBack hash;
} CFSetCallBacks;
There're predefined sets of callbacks like:
/*!
#constant kCFTypeSetCallBacks
Predefined CFSetCallBacks structure containing a set of callbacks
appropriate for use when the values in a CFSet are all CFTypes.
*/
CF_EXPORT
const CFSetCallBacks kCFTypeSetCallBacks;
Which means that you're not forced to provide all of them, but you're free to modify just some of them. Let's prepare two callback functions:
// typedef CFHashCode (*CFSetHashCallBack)(const void *value);
CFHashCode runningApplicationBundleIdentifierHash(const void *value) {
NSRunningApplication *application = (__bridge NSRunningApplication *)value;
return [application.bundleIdentifier hash];
}
// typedef Boolean (*CFSetEqualCallBack)(const void *value1, const void *value2);
Boolean runningApplicationBundleIdentifierEqual(const void *value1, const void *value2) {
NSRunningApplication *application1 = (__bridge NSRunningApplication *)value1;
NSRunningApplication *application2 = (__bridge NSRunningApplication *)value2;
return [application1.bundleIdentifier isEqualToString:application2.bundleIdentifier];
}
You can use them in this way:
- (NSMutableSet<NSRunningApplication *> *_Nullable)bundleIdentifierAwareMutableSetWithCapacity:(NSUInteger)capacity {
// > Predefined CFSetCallBacks structure containing a set of callbacks
// > appropriate for use when the values in a CFSet are all CFTypes.
//
// Which means that you shouldn't bother about retain, release, ... callbacks,
// they're already set.
//
// CFSetCallbacks can be on stack, because this structure is copied in the
// CFSetCreateMutable function.
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
// Overwrite just the hash & equal callbacks
callbacks.hash = runningApplicationBundleIdentifierHash;
callbacks.equal = runningApplicationBundleIdentifierEqual;
// Try to create a mutable set.
CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, capacity, &callbacks);
if (set == NULL) {
// Failed, do some error handling or just return nil
return nil;
}
// Transfer the ownership to the Obj-C & ARC => no need to call CFRelease
return (__bridge_transfer NSMutableSet *)set;
}
&
NSMutableSet<NSRunningApplication *> *set = [self bundleIdentifierAwareMutableSetWithCapacity:50];
[set addObjectsFromArray:[[NSWorkspace sharedWorkspace] runningApplications]];
NSLog(#"%#", set);
I'm converting some older Obj-C code to ARC. I have this method:
+ (NSString **)sortKeys
{
return sortKeys;
}
And the complaint I get from the automatic conversion is:
Cannot initialize return object of type 'NSString *__autoreleasing *' with an lvalue of type 'NSString *__strong [14]
The sortKeys are declared as:
static NSString *sortKeys[] = {
NAME_KEY,
CATNUM_KEY,
CATNUM_NAME_KEY,
MAG_KEY,
DISTANCE_KEY,
CONST_KEY,
RISE_KEY,
TRANSIT_KEY,
SET_KEY,
RA_KEY,
DEC_KEY,
AZM_KEY,
ALT_KEY,
DATE_KEY
};
There is also a complaint when called:
NSString **sortKeys = [ListMgr sortKeys];
I don't want to transfer any ownership of the string, I just want the caller to be able to iterate through them.
How do I declare the method when using ARC?
This is not compatible with ARC. It can't safely memory-manage this. You're relying on behaviors that aren't promised (so this has always been unsafe). sortKeys is not retaining NAME_KEY, so there is no promise that NAME_KEY will be valid. You may happen to have special knowledge that it will be (because NAME_KEY is a static string for instance), but this is an implementation detail, not a language promise. ARC is about guarantees, not "we happen to get away with it."
The correct tool for this is an NSArray:
#interface MyClass : NSObject
+ (NSArray<NSString *>*)sortKeys;
#end
#implementation MyClass
static NSArray<NSString *>* sortKeys;
+ (void)initialize {
if (self == [MyClass class] ) {
sortKeys = #[ ... ];
}
}
+ (NSArray<NSString *>*)sortKeys
{
return sortKeys;
}
#end
If you can't change the interface, then you'll have to keep this definition in an non-ARC file.
I kept your caching here, but if you don't call +sortKeys very often, I'd get rid of the static and just construct a new array each time. It's quite cheap, especially in this case:
#interface MyClass : NSObject
+ (NSArray<NSString *>*)sortKeys;
#end
#implementation MyClass
+ (NSArray<NSString *>*)sortKeys
{
return #[...]; // Just put your value here; then it's simple
}
#end
You can use NSArray's initWithObjects:count: initializer to convert from the C array into a Foundation one, and create an ARC, memory-safe array:
+ (NSArray<NSString *> *)sortKeys
{
static NSArray<NSString *> *convertedKeys;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
convertedKeys = [[NSArray alloc] initWithObjects:sortKeys count:sizeof(sortKeys)];
});
return convertedKeys;
}
You'd get ARC-compatible code, and also you can access NSArray's helpers methods - like count, fast enumeration, etc.
I am very much new to objective-c and I'm struggling with this problem for a while! Here is my class prototype:
#interface YoCatchModel : NSObject
/**
Name of the Yo user. Currently this is local
*/
#property (nonatomic, strong) NSString* username;
/**
History of the messages sent with Yo
*/
#property (nonatomic, strong, readonly) NSMutableArray* historyArray;
/*
implement init method
*/
+ (instancetype) initmethod;
I should allocate memory for my history mutable array in this method which is read only.
I want to make another init method that takes a username string parameter. This new initWithUsername method should call init within its definition.
And here is implementation which I am trying to implement an init method using instancetype as the return type. But I am not really sure how to
Allocate memory for the array.
Call another init method for the user name.
#implementation YoCatchModel
+ (instancetype)initmethod {
return [[[self class] alloc] init];
}
I appreciate if anyone can give me some hint how to do this. So far I have read these pages to get to here:
http://www.techotopia.com/index.php/An_Overview_of_Objective-C_Object_Oriented_Programming#Declaring.2C_Initializing_and_Releasing_a_Class_Instance
https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW7
https://developer.apple.com/library/ios/releasenotes/ObjectiveC/ModernizationObjC/AdoptingModernObjective-C/AdoptingModernObjective-C.html#//apple_ref/doc/uid/TP40014150-CH1-SW11
The initWithUsername method becomes the designated initializer of your class and would look something like:
- (instancetype)initWithUsername:(NSString *)username
{
self = [super init];
if (self) {
_username = [username copy];
_historyArray = [NSMutableArray new];
}
return self;
}
You should make the default init method use the designated initializer:
- (instancetype)init
{
return [self initWithUsername:nil];
}
and note that this code works on the property backing instance variables, which start with _, rather than using self. (which won't work with a readonly property anyway), and this is to avoid possible KVO side-effects of the property setter methods.
While I'm more than familiar with C#, I'm totally new at Objective C and iOS development. So I'm learning the language. What I don't understand is why the following code throws a compiler error (and yes, this is from the exercises at Programming with Objective C:
SNDPerson:
#interface SNDPerson : NSObject
#property NSString *first;
#property NSString *last;
+ (SNDPerson *)person;
#end
#implementation SNDPerson
+ (SNDPerson *)person
{
SNDPerson *retVal = [[self alloc] init];
retVal.first = #"Ari";
retVal.last = #"Roth";
return retVal;
}
#end
SNDShoutingPerson:
#import "SNDPerson.h"
#interface SNDShoutingPerson : SNDPerson
#end
#implementation SNDShoutingPerson
// Implementation is irrelevant here; all it does is override a method that prints a string
// in all caps. This works; I've tested it. However, if necessary I can provide more code.
// The goal here was for a concise repro.
#end
Main method:
- int main(int argc, const char * argv[])
{
SNDShoutingPerson *person = [[SNDShoutingPerson alloc] person]; // Error
...
}
The error is "No visible #interface for "SNDShoutingPerson" declares the selector "person".
Shouldn't this work? SNDShoutingPerson inherits from SNDPerson, so I would have assumed it got access to SNDPerson's class factory methods. Did I do something wrong here, or do I have to declare the method on SNDShoutingPerson's interface as well? The exercise text implies that what I did should Just Work.
Omit the +alloc when calling the class method:
SNDShoutingPerson *person = [SNDShoutingPerson person];
Briefly:
+ (id)foo denotes a class method. This takes the form:
[MONObject method];
- (id)foo denotes an instance method. This takes the form:
MONObject * object = ...; // << instance required
[object method];
Also, you can declare + (instancetype)person in this case, rather than + (SNDPerson *)person;.
change the line SNDShoutingPerson *person = [[SNDShoutingPerson alloc] person]; // Error
to
SNDShoutingPerson *person = [[SNDShoutingPerson alloc] init];
Cheers.
If you want to call class method:
SNDPerson person = [SNDPerson person];
person is a class method, but you're trying to call it with the incompletely constructed instance returned by alloc. Kill the alloc and just do [SNDShoutingPerson person].
This has nothing to do with subclasses, by the way. You would get the same error if you had written [[SNDPerson alloc] person].
As you know Apple has provided # literals for such classes as NSNumber, NSDictionary, NSArray, so we can create an object this way, for example
NSArray *array = #[obj1, obj2];
So I wonder, if there is a way to create such literals for my own classes? For example, I want to write smth. like
MyClass *object = MyClass[value1, value2];
And I don't want to write long parsers :)
# syntax are literals, which is feature of Clang compiler. Since its compiler feature, NO, you cannot define you own literals.
For more informations about compilers literals, please refer Clang 3.4 documentation - Objective-C Literals
Edit: Also, I just found this interesting SO discussion
Edit: As BooRanger mentioned at the comments, there exists method to create [] accessors (the Collection Literals way) to access custom objects. Its called Object Subscripting. Using this, you can access anything in your custom class like this myObject[#"someKey"]. Read more at NSHipster.
Here is my example implementation of "Subcriptable" object. For example simplicity, it just accesses internal dictionary. Header:
#interface LKSubscriptableObject : NSObject
// Object subscripting
- (id)objectForKeyedSubscript:(id <NSCopying>)key;
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key;
#end
Implementation:
#implementation LKSubscriptableObject {
NSMutableDictionary *_dictionary;
}
- (id)init
{
self = [super init];
if (self) {
_dictionary = [NSMutableDictionary dictionary];
}
return self;
}
- (id)objectForKeyedSubscript:(id <NSCopying>)key
{
return _dictionary[key];
}
- (void)setObject:(id)obj forKeyedSubscript:(id <NSCopying>)key
{
_dictionary[key] = obj;
}
#end
You can then access to anything in this object simply using square brackets:
LKSubscriptableObject *subsObj = [[LKSubscriptableObject alloc] init];
subsObj[#"string"] = #"Value 1";
subsObj[#"number"] = #2;
subsObj[#"array"] = #[#"Arr1", #"Arr2", #"Arr3"];
NSLog(#"String: %#", subsObj[#"string"]);
NSLog(#"Number: %#", subsObj[#"number"]);
NSLog(#"Array: %#", subsObj[#"array"]);
Are you okay with this syntax?
MyClass *object = MyClass(value1, value2);
Just define macro like this:
#define MyClass(objects...) [[MyClass alloc] initWithObjects: #[objects]];
Compiler will allow class named MyClass and MyClass() macro.